package com.dongnam.quanly.web.privilege;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
import javax.faces.model.SelectItem;

import org.hibernate.Session;
import org.primefaces.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dongnam.quanly.common.ActionCategory;
import com.dongnam.quanly.common.ActionType;
import com.dongnam.quanly.common.ObjectType;
import com.dongnam.quanly.common.ScreenId;
import com.dongnam.quanly.common.ScreenType;
import com.dongnam.quanly.common.controller.ConsoleUserController;
import com.dongnam.quanly.common.controller.UserGroupController;
import com.dongnam.quanly.common.dao.ConsoleUserDao;
import com.dongnam.quanly.common.dao.UserGroupDao;
import com.dongnam.quanly.common.dao.UserRoleDao;
import com.dongnam.quanly.common.exceptions.DatabaseException;
import com.dongnam.quanly.common.model.ConsoleUser;
import com.dongnam.quanly.common.model.UserGroup;
import com.dongnam.quanly.common.model.UserRight;
import com.dongnam.quanly.common.model.UserRole;
import com.dongnam.quanly.common.model.UserRight.AccessLevel;
import com.dongnam.quanly.common.persistence.HibernateUtil;
import com.dongnam.quanly.common.util.CryptUtil;
import com.dongnam.quanly.web.DiffComparator;
import com.dongnam.quanly.web.JSFUtility;
import com.dongnam.quanly.web.UserBean;
import com.dongnam.quanly.web.audit.AuditLogger;
import com.dongnam.quanly.web.util.LocaleMessages;
import com.dongnam.quanly.web.util.MessageUtil;

@ManagedBean(name = "ConsoleUsersBean")
@ViewScoped
public class ConsoleUsersBean implements Serializable {

	private static final long serialVersionUID = 1L;
	private static final Logger logger = LoggerFactory.getLogger(ConsoleUsersBean.class);

	@ManagedProperty(value = "#{UserBean}")
	private UserBean userBean;

	private List<ConsoleUser> consoleUsers;
	private List<UserGroup> userGroups;

	private ConsoleUser selectedUser;
	private String selectedUserGroupName;
	private String password;
	private String confirmPassword;
	private ConsoleUser prev;
	private List<UserRight> userRights;

	@SuppressWarnings("unused")
	private List<ConsoleUser.Status> statuses;
	private List<String> userGroupNames;
	private HashMap<String, UserGroup> userGroupNameMap;

	private boolean createNew;
	private List<SelectItem> statusOptions;

	public ConsoleUsersBean() {
		setConsoleUsers(new ConsoleUserController().findAll());
		setUserGroups(new UserGroupController().findAll());
	}

	// actions //

	public void selectUser(ConsoleUser user) {
		selectedUser = user;
		confirmPassword = decrypt(user.getPassword());
		fetchUserRights();
	}

	public void addUser() {
		logger.debug("addUser");
		selectedUser = new ConsoleUser();
		password = null;
		confirmPassword = null;
	}

	private void fetchUserRights() {
		if (selectedUser != null) {
			logger.debug("getting user rights...target=" + selectedUser.getUserId() + " ,group="
					+ selectedUser.getGroup());
			List<UserRight> list = new ArrayList<UserRight>();
			UserGroup group = selectedUser.getGroup();
			if (group != null) {
				// logger.debug("group not null");
				// logger.debug("group id="+group.getGroupId());
				boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
				Session groupSession = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil
						.startCurrentSession();

				List<UserRole> roles = null;
				try {
					new UserGroupDao().makePersistent(group);
					roles = new ArrayList<UserRole>(group.getUserRoles());
					for (UserRole role : roles) {
						// logger.debug("role detected:"+role.getRoleName());
						List<UserRight> rights = null;
						new UserRoleDao().makePersistent(role);
						rights = new ArrayList<UserRight>(role.getUserRights());
						Collections.sort(rights);
						list.addAll(role.getUserRights());
					}
					if (!isTransacted) {
						groupSession.getTransaction().commit();
					}

				} catch (Throwable e) {
					if (!isTransacted && groupSession.isOpen() && !groupSession.getTransaction().wasCommitted())
						groupSession.getTransaction().rollback();
				}
				// logger.debug("role count="+roles.size());
				// for (UserRole role : roles) {
				// logger.debug("role detected:"+role.getRoleName());
				// Session roleSession = HibernateUtil.startCurrentSession();
				// List<UserRight> rights = null;
				// try {
				// new UserRoleDao().makePersistent(role);
				// rights = new ArrayList<UserRight>(role.getUserRights());
				// Collections.sort(rights,new UserRightComparator());
				// roleSession.getTransaction().commit();
				// } catch (Throwable e) {
				// roleSession.getTransaction().rollback();
				// }
				// list.addAll(role.getUserRights());
				// }
				// after getting a list of user rights that belongs to user,
				// find max right per page
				Map<ScreenType, AccessLevel> userAccessMap = new HashMap<ScreenType, AccessLevel>();
				for (ScreenType type : ScreenType.getEnum("A")) {
					// initialize to prevent the unmatch of user roles & screen
					// type
					userAccessMap.put(type, AccessLevel.NONE);
				}
				for (UserRight right : list) {
					if (userAccessMap.containsKey(right.getTypeId())) {
						// if access level in roleList is higher, put that into
						// the access map, else do nothing
						if (right.getAccessLevel().ordinal() > userAccessMap.get(right.getTypeId()).ordinal()) {
							userAccessMap.put(right.getTypeId(), right.getAccessLevel());
						}
					} else { // put access right into map if it doesnt exist yet
						userAccessMap.put(right.getTypeId(), right.getAccessLevel());
					}
				}
				// now the map should be filled with most effective rights
				list.clear();
				List<ScreenType> screenTypes = new ArrayList<ScreenType>(userAccessMap.keySet());
				Collections.sort(screenTypes);
				for (ScreenType type : screenTypes) {
					// adding a dummy user right as it will never be saved
					list.add(new UserRight(type, userAccessMap.get(type), null));
					// logger.debug("adding right: screenType="+type+", level="+userAccessMap.get(type));
				}
			} else {
				logger.warn("group is null. This should not be happening.");
			}
			userRights = list;
		}
	}

	public void deleteUser() {
		logger.debug("deleteUser: {}", selectedUser);
		if (selectedUser != null && selectedUser.getUserId().equals("SYS_DEFAULT")) {
			logger.debug("Attempt to delete SYS_DEFAULT.");
			addErrorMsg("general_delete_failed", LocaleMessages.getString("privilege_userDeleteFailure"), new Date());
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE, false,
					ObjectType.User, selectedUser.getUserId(), LocaleMessages.getString("privilege_userDeleteFailure"),
					null, null);
		} else {
			boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
			Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

			try {
				new ConsoleUserDao().makeTransient(selectedUser);
				AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE,
						true, ObjectType.User, selectedUser.getUserId(), null, null, null);
				// new ConsoleUserController().makeTransient(selectedUser);
				if (!isTransacted) {
					session.getTransaction().commit();
				}
			} catch (DatabaseException dbe) {
				if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
					session.getTransaction().rollback();
				addErrorMsg("general_delete_failed", MessageUtil.getDescriptionOfDBException(dbe), new Date());
				AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE, false,
						ObjectType.User, selectedUser.getUserId(), MessageUtil.getDescriptionOfDBException(dbe), null,
						null);
				MessageUtil.logDBException("Delete user", dbe);
				refreshUsers();
				return;
			}
			String msg = LocaleMessages.getString("general_delete_success", selectedUser.getUserId());
			JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_INFO, msg, msg);
			refreshUsers();

		}
	}

	public synchronized void saveUser() {
		logger.debug("saveUser: {}", selectedUser);
		RequestContext context = RequestContext.getCurrentInstance();
		if (!createNew) {
			try {
				prev = new ConsoleUserController().findById(selectedUser.getUserId(), false);
				prev.setGroupId(selectedUser.getGroup().getGroupId());
			} catch (DatabaseException e) {
				addErrorMsg("general_add_edit_fail", MessageUtil.getDescriptionOfDBException(e), new Date());
				AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE,
						createNew ? ActionType.CREATE : ActionType.UPDATE, false, ObjectType.User,
						String.valueOf(selectedUser.getUserId()).equals("null") ? null : selectedUser.getUserId(),
						MessageUtil.getDescriptionOfDBException(e), null, null);
				MessageUtil.logDBException("Save user", e);
				refreshUsers();
				selectedUser = new ConsoleUser();
				return;
			}
		}
		if (failsValidation(selectedUser)) {
			context.addCallbackParam("opSuccess", false);
			refreshUsers();
			selectedUser = new ConsoleUser();
			return;
		}

		if (!password.equals(encrypt(confirmPassword))) {
			context.addCallbackParam("opSuccess", false);
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, createNew ? ActionType.CREATE
					: ActionType.UPDATE, false, ObjectType.User, selectedUser.getUserId(), null, null, null);
			String msg = LocaleMessages.getString("privilege_userwrongconfirmpassword");
			JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg);
			refreshUsers();
			selectedUser = new ConsoleUser();
			return;
		}

		selectedUser.setGroup(getUserGroupNameMap().get(selectedUserGroupName));
		selectedUser.setGroupId(selectedUser.getGroup().getGroupId());
		selectedUser.setPassword(password);
		selectedUser.setLastUpdatedBy(userBean.getUser().getUserId());
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			// new ConsoleUserController().makePersistent(selectedUser);
			new ConsoleUserDao().makePersistent(selectedUser);
			// log
			String[] differences = { "", "" };
			if (!createNew) {
				DiffComparator diff = new DiffComparator();
				differences = diff.compare(ObjectType.User, prev, selectedUser);
			}
			AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE,
					createNew ? ActionType.CREATE : ActionType.UPDATE, true, ObjectType.User, selectedUser.getUserId(),
					null, differences[0], differences[1]);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Exception e1) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			DatabaseException e = new DatabaseException(e1);
			addErrorMsg("general_add_edit_fail", MessageUtil.getDescriptionOfDBException(e), new Date());
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, createNew ? ActionType.CREATE
					: ActionType.UPDATE, false, ObjectType.User, String.valueOf(selectedUser.getUserId())
					.equals("null") ? null : selectedUser.getUserId(), MessageUtil.getDescriptionOfDBException(e),
					null, null);
			MessageUtil.logDBException("saveUser", e);
			prev = null;
			selectedUser = new ConsoleUser();
			refreshUsers();
			context.addCallbackParam("opSuccess", false);
			return;
		}
		String msg = LocaleMessages.getString("general_add_edit_success", selectedUser.getUserId());
		JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_INFO, msg, msg);
		refreshUsers();
		prev = null;
		password = null;
		confirmPassword = null;
		selectedUser = null;
		context.addCallbackParam("opSuccess", true);
	}

	public void refreshUsers() {
		setConsoleUsers(new ConsoleUserController().findAll());
	}

	public String decrypt(String password) {
		try {
			if (password != null)
				return CryptUtil.decrypt(password);
			else
				return null;
		} catch (Exception e) {
			// e.printStackTrace();
			logger.error("decrypt", e);
			return null;
		}
	}

	public String encrypt(String password) {
		try {
			if (password != null)
				return CryptUtil.encrypt(password);
			else
				return null;
		} catch (Exception e) {
			// e.printStackTrace();
			logger.error("encrypt", e);
			return null;
		}
	}

	private String addErrorMsg(String msgKey, String e, Date date) {
		String message = LocaleMessages.getString(msgKey, e, date);
		JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
		return message;
	}

	// validation //

	public boolean failsValidation(ConsoleUser user) {
		return userIdExistsAlready(user);
	}

	public boolean userIdExistsAlready(ConsoleUser newUser) {
		for (ConsoleUser user : consoleUsers) {
			if (user.getUserId().equals(newUser.getUserId()) && user != newUser) {
				String message = LocaleMessages.getString("privilege_userExistsAlready", user.getUserId());
				JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
				AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE,
						createNew ? ActionType.CREATE : ActionType.UPDATE, false, ObjectType.User, createNew ? null
								: selectedUser.getUserId(), message, null, null);
				return true; // userid exists already, bad
			}
		}
		return false; // userid doesnt exist yet, ok
	}

	// getters setters //

	public List<ConsoleUser> getConsoleUsers() {
		return consoleUsers;
	}

	public void setConsoleUsers(List<ConsoleUser> consoleUsers) {
		this.consoleUsers = consoleUsers;
	}

	public List<UserGroup> getUserGroups() {
		if (userGroups == null) {
			userGroups = (new UserGroupController()).findAll();
			return userGroups;
		}
		return userGroups;
	}

	public void setUserGroups(List<UserGroup> userGroups) {
		this.userGroups = userGroups;
	}

	public ConsoleUser getSelectedUser() {
		return selectedUser;
	}

	public void setSelectedUser(ConsoleUser selectedUser) {
		this.selectedUser = selectedUser;
	}

	public List<ConsoleUser.Status> getStatuses() {
		return statuses = Arrays.asList(ConsoleUser.Status.values());
	}

	public HashMap<String, UserGroup> getUserGroupNameMap() {
		if (userGroupNameMap == null) {
			userGroupNameMap = new HashMap<String, UserGroup>();
			for (UserGroup group : getUserGroups()) {
				userGroupNameMap.put(group.getGroupName(), group);
			}
		}
		return userGroupNameMap;
	}

	public synchronized List<String> getUserGroupNames() {
		if (userGroupNames == null) {
			userGroupNames = new ArrayList<String>();
			for (UserGroup group : getUserGroups()) {
				userGroupNames.add(group.getGroupName());
			}
		}
		// logger.debug("selectedUser: {}", selectedUser);
		// if (selectedUser != null)
		// logger.debug("getGroupId: {}", selectedUser.getGroup());
		if (selectedUser != null && selectedUser.getGroup() != null) {
			logger.debug("group: {}", selectedUser.getGroup());
			userGroupNames.remove(selectedUser.getGroup().getGroupName());
			userGroupNames.add(0, selectedUser.getGroup().getGroupName());
		}
		// logger.debug("userGroupnames: {}",userGroupNames);
		return userGroupNames;
	}

	public String getSelectedUserGroupName() {
		return selectedUserGroupName;
	}

	public void setSelectedUserGroupName(String selectedUserGroupName) {
		this.selectedUserGroupName = selectedUserGroupName;
	}

	public String getPassword() {
		if (selectedUser != null && selectedUser.getPassword() != null) {
			password = decrypt(selectedUser.getPassword());
		}
		return password;
	}

	public void setPassword(String password) {
		if (selectedUser != null) {
			password = encrypt(password);
		}
		this.password = password;
	}

	public void setUserBean(UserBean userBean) {
		this.userBean = userBean;
	}

	public boolean isCreateNew() {
		return createNew;
	}

	public void setCreateNew(boolean createNew) {
		this.createNew = createNew;
	}

	public String getConfirmPassword() {
		return confirmPassword;
	}

	public void setConfirmPassword(String confirmPassword) {
		this.confirmPassword = confirmPassword;
	}

	public List<SelectItem> getStatusOptions() {
		if (statusOptions == null) {
			statusOptions = new ArrayList<SelectItem>();
			statusOptions.add(new SelectItem(null, ""));
			for (ConsoleUser.Status status : getStatuses()) {
				statusOptions.add(new SelectItem(status, status.name()));
			}
		}
		return statusOptions;
	}

	public String getUserGroupId(ConsoleUser consoleUser) {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		String groupId = "";
		try {
			new ConsoleUserDao().makePersistent(consoleUser);
			groupId = consoleUser.getGroup().getGroupId();
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Exception e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
		}
		return groupId;

	}

	public List<UserRight> getUserRights() {
		return userRights;
	}
}
